package pl.smath.operation.basic;

import java.util.List;
import java.util.Vector;

import pl.smath.expression.NumberTerm;
import pl.smath.expression.ProductTerm;
import pl.smath.expression.SumTerm;
import pl.smath.expression.SymbolTerm;
import pl.smath.expression.Term;


/**
 * Zawiera podstawowe operacje no modelu.
 * 
 * @author klangner
 *
 */
public class AddOp extends Operation{

	/**
	 * Ta funkcja gwarantuje ze wyrazenie zostanie dodane.
	 * Jezeli nie bedzie to mozliwe to zostanie zwrocone wyrazenie typu t1+t2
	 */
	public Term forceAdd(Term t1, Term t2){
		Term outputTerm = process(t1, t2);
		
		if(outputTerm == null){
		
			SumTerm pt = new SumTerm();
			pt.addTerm(t1);
			pt.addTerm(t2);
			outputTerm = pt;
		}
		
		
		return outputTerm;
	}
	
	
	/**
	 * Dodaje do siebie 2 wyrazenie. Jezeli wyrazenia nie moga byc dodane to zwracana jest wartosc null.
	 *  
	 * @param t1
	 * @param t2
	 * @return Nowy obiekt bedacy sumą wyrazen lub null jezeli wyrazenie nie moga byc dodane
	 */
	public Term process(Term t1, Term t2){
		
		float v1 = t1.isNegative()?-1:1;

		if(t1 instanceof NumberTerm && t2 instanceof NumberTerm){
		
			// Dodajemy do siebie 2 liczby
			float value = ((NumberTerm)t1).getValue() + ((NumberTerm)t2).getValue();
			
			return new NumberTerm(value);
		}
		else if(t1 instanceof SymbolTerm){
			SymbolTerm symbol = (SymbolTerm)t1;
			
			if(t2 instanceof SymbolTerm && t1.isTheSame(t2)){
				
				return addTheSameTerms(v1, t2.isNegative()?-1:1, symbol);
			}
			else if(t2 instanceof ProductTerm){
				return addSymbol2Product((SymbolTerm)t1, (ProductTerm)t2);
			}
		}
		else if(t1.isTheSame(t2)){
			return addTheSameTerms(v1, t2.isNegative()?-1:1, t1);
		}
		else if(t1 instanceof ProductTerm){
			if(t2 instanceof ProductTerm) 
				return addProducts((ProductTerm)t1, (ProductTerm)t2);
			else
				return addSymbol2Product(t2, (ProductTerm)t1);
		}
		else if(t2 instanceof ProductTerm){
			return addSymbol2Product(t1, (ProductTerm)t2);
		}
		
		return null;
	}
	
	
	/**
	 * Zamiana listy podwyrazen na wyrazenia. Implementacja zalezy od operacji
	 * @param terms
	 * @return
	 */
	protected Term mergeTerms(List<Term> terms){
		SumTerm sumTerm = new SumTerm();
		
		// skopiowanie wszystkich niezerowych wyrazen
		for(int i = 0; i < terms.size(); i++){
			
			Term t = terms.get(i);
			if(!(t instanceof NumberTerm) || ((NumberTerm)t).getValue() != 0){
				sumTerm.addTerm(t);
			}
		}
		
		if(sumTerm.getTermsList().size() == 0)
			return new NumberTerm(0);
			
		return sumTerm;
		
	}
	
	
	/**
	 * można dodac tylko w sytuacji gdy sa własną wielokrotnościa
	 * @param t1
	 * @param t2
	 * @return
	 */
	private Term addProducts(ProductTerm t1, ProductTerm t2) {
		NumberTerm n1 = null;
		NumberTerm n2 = null;
		Vector<Term> terms1 = new Vector<Term>();
		Vector<Term> terms2 = new Vector<Term>();
		
		terms1.addAll(t1.getTermsList());
		terms2.addAll(t2.getTermsList());
		
		// znalezienie 1 wezła numerycznego
		for(int i = 0; i < terms1.size(); i++){
			if(terms1.get(i) instanceof NumberTerm){
				n1 = (NumberTerm)terms1.get(i).copy();
				terms1.remove(i);
				break;
			}
		}
		
		if(n1 == null)
			n1 = new NumberTerm(1);

		if(t1.isNegative())
			n1.setNegative(!n1.isNegative());

		
		// znalezienie 2 wezła numerycznego
		for(int i = 0; i < terms2.size(); i++){
			if(terms2.get(i) instanceof NumberTerm){
				n2 = (NumberTerm)terms2.get(i).copy();
				terms2.remove(i);
				break;
			}
		}
		
		if(n2 == null)
			n2 = new NumberTerm(1);

		if(t2.isNegative())
			n2.setNegative(!n2.isNegative());

		if(terms1.size() != terms2.size())
			return null;
		
		for(int i = 0; i < terms1.size(); i++){
			
			Term srcTerm = terms1.get(i);
			boolean found = false;
			for(int j = 0; j < terms2.size(); j++){
				
				if(srcTerm.isTheSame(terms2.get(j))){
					terms2.remove(j);
					found = true;
					break;
				}
				
				// Nie został znaleziony podobny element. Nie ma sensu porównywac dalej
				if(!found)
					break;
			}
		}

		if(terms2.isEmpty()){
		
			// Jezeli ta lista jest pusta to sa wyrazenia mozna dodac
			NumberTerm t = new NumberTerm(n1.getValue() + n2.getValue());
			ProductTerm outputTerm = new ProductTerm();
			outputTerm.addTerm(t);
			outputTerm.addTerms(terms1);
			
			return outputTerm;
		}
		
		
		return null;
	}


	/**
	 * dodaje do siebie wezel symbol i product.
	 * Aby wezly mogly byc dodane to:
	 *   * Product musi miec 2 wezlu w tym jeden numeryczny
	 *   * Drugi wezel to musi byc symbol i to taki sam jak wejsciowy
	 *   
	 * @param t2
	 * @param t1
	 */
	private Term addSymbol2Product(Term symbolTerm, ProductTerm productTerm) {

		List<Term> terms = productTerm.getTermsList();
		float v1 = symbolTerm.isNegative()?-1:1;
		
		if(terms.size() == 2){
			NumberTerm nt = null;
			if( terms.get(0) instanceof NumberTerm &&
				terms.get(1).isTheSame(symbolTerm))
			{
				nt = (NumberTerm) terms.get(0);
			}
			else if( terms.get(1) instanceof NumberTerm &&
					terms.get(0).isTheSame(symbolTerm))
			{
				nt = (NumberTerm) terms.get(1);
			}
			
			if(nt != null){
				float v2 = nt.getValue();
				Term st = symbolTerm.copy();
				
				st.setNegative(false);
				if(productTerm.isNegative())
					v2 = -v2;

				return addTheSameTerms(v1, v2, st);
			}
		}
		
		return null;
	}


	/** 
	 * Dodaje do siebie 2 symbole
	 * @param t1
	 * @param t2
	 */
	private Term addTheSameTerms(float v1, float v2, Term symbolTerm) {
		
		ProductTerm term;
		float v = v1+v2;
		Term newSymbolTerm = symbolTerm.copy();

		newSymbolTerm.setNegative(false);
		
		if(v < 0){
			term = new ProductTerm();
			term.setNegative(true);
			term.addTerm(new NumberTerm(-v));
			term.addTerm(newSymbolTerm);
		}
		else if(v > 0){
			term = new ProductTerm();
			term.setNegative(false);
			term.addTerm(new NumberTerm(v));
			term.addTerm(newSymbolTerm);
		}
		else{
			return new NumberTerm(0);
		}
		
		return term;
	}

}
